﻿//Funktion:        Funktionen zur (De)komprimierung/Ver- und Entschlüsselung von Dateien
//Sprache:         C# (Visual Studio 2008 Professional)
//Autor:           Dipl.-Ing. Andreas Maslo
//(c):             IngES/Dipl.-Ing. Andreas Maslo

using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text;

namespace dnpCryptCompress
{
  public class CryptCompress
  { 
    //Helferfunktionen Komprimierung/Dekomprimierung/Ver-/Entschlüsselung mit .NET 2.0 
    const string ZipHeader = "dnp_ZIP_CRYPT"; 
    //Platzhalter Kennwort 25 Zeichen, interne Speicherung als Hashwert (30 Zeichen)
    const string ZipPw = "*52dirs3oGJUefZwq@-~17sxÜ";
    // Dateikürzel pszip (PowerShell-ZIP-Format mit optionaler Verschlüsselungsfunktion,
    //aufgrund benutzerdefiniertem Header kein Standard-ZIP-Format)
    const string ZipSuffix = ".dnpzip";
    const string CryptSuffix = ".dnpcrypt";  
    
    
    public byte[] GetFile(string file, bool ReducedByHeader, ref string PasswordHash) 
    { 
        //Datei binär in Byte-Array einlesen (Annahme, die Datei kann in der 
        //Gesamtheit im Speicher verarbeitet werden; kein blockweises Lesen) 
        if (System.IO.File.Exists(file)) { 
            FileStream fs = new FileStream(file, FileMode.Open); 
            if (ReducedByHeader) { 
                //Header ggfs. überlesen 
                //enstprechend der Länge der Konstanten 'ZipHeader' 
                fs.Position = ZipHeader.Length; 
                //Kennwort auslesen und in Hashwert-Zeichenkette umwandeln 
                byte[] pw = new byte[30];
                fs.Read(pw, 0, 30);
                PasswordHash = ByteArray2Str(pw);
                PasswordHash = PasswordHash.Trim(); 
                //Daten
                byte[] fBytes = new byte[FileLen(file)-30-ZipHeader.Length];
                fs.Read(fBytes, 0, fBytes.Length);
                fs.Close();
                return fBytes; 
            } 
            else {
                //übergebenes Kennwort wird nicht verändert
                byte[] fBytes = new byte[FileLen(file)]; 
                fs.Read(fBytes, 0, fBytes.Length);
                fs.Close();
                return fBytes;
            } 
        } 
        else { 
            return null; 
        } 
    }

    public string GetFile(string file)
    {
        //Datei binär in Zeichenkette einlesen
        //(Annahme, die Datei kann in der Gesamtheit im Speicher verarbeitet werden; kein blockweises Lesen) 
        string fData;
        if (System.IO.File.Exists(file)) 
           {
            byte[] fBytes = new byte[FileLen(file)];
            FileStream fs = new FileStream(file, FileMode.Open);
            fs.Read(fBytes, 0, fBytes.Length);
            fData = ByteArray2Str(fBytes);
            fs.Close();
            return fData;
           }
        else
        {
            return "";
        }
    } 

    public void SaveFile(string file, byte[] data)
    {
       //Byte-Datenfeld in neue Datei schreiben
       using(BinaryWriter binWriter = new BinaryWriter(File.Open(file, FileMode.Create)))
        {
         binWriter.Write(data);
         binWriter.Close();
        }

    }
    
    public byte[] Str2ByteArray(string str) 
     { 
        //Zeichenkette in Byte-Array umwandeln
        System.Text.ASCIIEncoding obj = new System.Text.ASCIIEncoding(); 
        return obj.GetBytes(str); 
    } 
    
    public string ByteArray2Str(byte[] ByteArray) 
    {
        //Byte-Array in Zeichenkette umwandeln
        System.Text.ASCIIEncoding eObj = new System.Text.ASCIIEncoding(); 
        return eObj.GetString(ByteArray); 
    }

     public string FileWithIndex(string file) 
    { 
        //automatisierte Indizierung erlaubt ein mehrfaches entpacken im Zielpfad 
        string fName = ""; 
        int Counter = 0; 
        string Suffix = ""; 
        string MainFileName = ""; 
        if (file.IndexOf(".") > 0) { 
            Suffix = file.Substring(file.LastIndexOf(".")); 
            MainFileName = file.Substring(0, file.Length - Suffix.Length); 
            //Dateinamen variieren, bis ein Dateiname erzeugt wurde, der nicht existiert 
            do { 
                Counter += 1; 
                fName = MainFileName + Counter + Suffix; 
            } 
            while (File.Exists(fName)); 
            return fName; 
        } 
        else { 
            //neuer Dateiname ohne Suffix finden und zurückgeben 
            do { 
                Counter += 1; 
                fName = fName + Counter; 
            } 
            while (File.Exists(fName)); 
            return fName; 
        } 
    } 
    
    public string SizeInfo(string file) 
    {
        //Größe einer übegebenen Datei ermitteln und als Informationszeichenkette zurückliefern
        if (File.Exists(file)) { 
            FileInfo fObj = new FileInfo(file); 
            return fObj.Length + " Byte"; 
        } 
        else { 
            return 0 + " Byte"; 
        } 
    }

    public long FileLen(string file)
    {
        //Größe einer übegebenen Datei in Byte ermitteln
        if (File.Exists(file))
        {
            FileInfo fObj = new FileInfo(file);
            return fObj.Length;
        }
        else
        {
            return 0;
        }
    }

    public string String2Hash(string Value) 
    { 
        //Kennwort in Hashwert umwandeln 
        //Hashwert kann nicht in Kennwort zurückübersetzt werden
        byte[] StrBytes = System.Text.Encoding.UTF8.GetBytes(Value); 
        SHA1CryptoServiceProvider hAlgorithm = new SHA1CryptoServiceProvider(); 
        byte[] hBytes = hAlgorithm.ComputeHash(StrBytes); 
        hAlgorithm.Clear(); 
        string erg = Convert.ToBase64String(hBytes); 
        return erg; 
    }

    public string Compress(string file, string pw) 
    { 
     //Datei komprimieren und ggfs. mit Kennwort sichern
     if (File.Exists(file))
      {
       //Archiv wird im Originalverzeichnis mit neuem Suffix gesichert 
       string NewName = file + ZipSuffix;
       if (File.Exists(NewName))
        {
         //kein Komprimieren, wenn Archiv bereits existiert 
         return "Die Archivdatei '" + NewName + "' zur Datei '" + 
          file + "' existiert bereits. Eine Komprimierung wurde " + 
          "nicht durchgeführt!";
        }
       else
        {
         //in MemoryStream komprimieren 
         MemoryStream ms = new MemoryStream();
         GZipStream cData = new GZipStream(ms, 
          CompressionMode.Compress, true);
         //gesamte Datei ohne Abspaltung eines Headers lesen
         byte[] oData = GetFile(file, false, ref pw);
         cData.Write(oData, 0, oData.Length);
         //komprimierten Stream in Byte-Datenfeld 
         cData.Dispose();
         ms.Position = 0;
         byte[] cDataBytes = new byte[ms.Length];
         //Datenfeld ist nullbasiert 
         ms.Read(cDataBytes, 0, cDataBytes.Length);
         ms.Dispose();
         //Dateistream um (un)komprimierte Daten zusammenzufügen 
         //Header unverschlüsselt (!) in Datei schreiben 
         FileStream cFile = new FileStream(NewName, FileMode.Create);
         string header = ZipHeader;
         byte[] byteheader = Str2ByteArray(header);
         cFile.Write(byteheader, 0, byteheader.Length);
         //Kennwort als Hashwert speichern 
         //(sofern nicht angegeben vordefiniertes Kennwort ablegen)
         //wobei die Datenlänge auf 30 Zeichen ausgelegt wird
         string fpw = "";
         if (pw == "")
          {
           fpw = String2Hash(ZipPw);
          }
         else
          {
           fpw = String2Hash(pw);
          }
         if (fpw.Length < 30)
          {
           //linksbündig auffüllen
           string fillstr = "";
           fillstr = fillstr.PadLeft(30 - fpw.Length, ' ');
           //Füllstring/Kennwort verketten (einheitliche Länge)
           fpw = fillstr + fpw;  
          }
         //Kennwort mit 30 Byte Länge schreiben
         byte[] pwh = Str2ByteArray(fpw);
         cFile.Write(pwh, 0, pwh.Length);
         //komprimierte Daten über memoryStream (!) schreiben 
         cFile.Write(cDataBytes, 0, cDataBytes.Length);
         cData.Close();
         cFile.Close();
         if (pw == "")
          {
           return "Die Datei '" + file + "' wurde in die Datei '" + 
            NewName + "' komprimiert.";
          }
         else
          {
           return "Die Datei '" + file + "' wurde in die Datei '" + 
            NewName + "' komprimiert und mit einem Kennwort " +
            "geschützt.";
          }
        }
      }
     else
      {
       //Quelldatei nicht vorhanden!
       return "Die angegebene Datei '" + file + 
        "' existiert nicht. Eine Komprimierung ist nicht möglich!"; 
      }
    }


    public string DeCompress(string file, string pw) 
    {
     string rValue = "Die angegebene Archivdatei '" + file + 
      "' exitisert nicht. Eine Dekomprimierung ist nicht möglich!";
     //Datei dekomprimieren
     if (System.IO.File.Exists(file)) { 
      if (IsCompressedOrCrypted(file)) { 
        //Dateiname ggfs. automatisiert mit Index versehen
        string uncFile = file.Replace(ZipSuffix, ""); 
        if (File.Exists(uncFile)) { 
         //dekomprimierte Datei mit Index versehen 
         uncFile = FileWithIndex(uncFile); 
        } 
        //komprimierte Daten lesen, dabei Header überlesen 
        string pwh = "";
        byte[] cData = GetFile(file, true, ref pwh); 
        //wurde ggfs. das erforderliche Kennwort angegeben
        if (IsCorrectPassword(pw, ref pwh) == false)  
         {
          //das erforderliche Kennwort wurde nicht angegeben
          return "Das für die Dekomprimierung übergebene Kennwort '" + 
           pw + "' ist nicht für die Dekomprimierung geeignet!";
         }
        //dekomprimieren per Memory/GZipStream 
        MemoryStream ms = new MemoryStream(cData); 
        GZipStream uData = new GZipStream(ms, 
         CompressionMode.Decompress); 
        uData.Flush(); 
        //dekomprimierte Längeninfo am Streamende auslesen 
        byte[] size = new byte[4]; 
        ms.Position = ms.Length - 4; 
        ms.Read(size, 0, 3); 
        ms.Flush(); 
        int uDataSize = BitConverter.ToInt32(size, 0); 
        //Daten der ermittelten Länge in Byte-Array einlesen 
        ms.Position = 0; 
        byte[] DecByteArray = new byte[uDataSize];
        try { 
         //dekomprimnierte Daten im Byte-Array DecByteArray
         uData.Read(DecByteArray, 0, uDataSize);
         } 
        catch (Exception ex) {
         //allgemeine Fehlermeldung beim Entschlüsseln
         Console.WriteLine(ex.Message);
        } 
        uData.Dispose(); 
        ms.Dispose(); 
        //Ergebnis auslesen und in Zieldatei ablegen 
        SaveFile(uncFile, DecByteArray);
        uData.Close();
        rValue = "Die Archivdatei '" + file + 
         "' wurde in die Datei '" + uncFile + "' dekomprimiert! ";
       } 
     }
     return rValue;
    }

    //Ver- und Entschlüsselungsfunktionen
    public byte[] Crypt(byte[] data, string pw)
    {
        //Zeichenkette mit eindeutigem Kennwort verschlüsseln
        RijndaelManaged rd = new RijndaelManaged();
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        byte[] key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pw));
        md5.Clear();
        rd.Key = key;
        rd.GenerateIV();
        byte[] iv = rd.IV;
        MemoryStream ms = new MemoryStream();
        ms.Write(iv, 0, iv.Length);
        CryptoStream cs = new CryptoStream(ms, rd.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(data, 0, data.Length);
        cs.FlushFinalBlock();
        byte[] encdata = ms.ToArray();
        cs.Close();
        rd.Clear();
        return encdata;
    }

    public string Crypt(string str, string pw)
    {
        //Zeichenkette mit eindeutigem Kennwort verschlüsseln
        RijndaelManaged rd = new RijndaelManaged();
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        byte[] key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pw));
        md5.Clear();
        rd.Key = key;
        rd.GenerateIV();
        byte[] iv = rd.IV;
        MemoryStream ms = new MemoryStream();
        ms.Write(iv, 0, iv.Length);
        CryptoStream cs = new CryptoStream(ms, rd.CreateEncryptor(), CryptoStreamMode.Write);
        byte[] data = System.Text.Encoding.UTF8.GetBytes(str);
        cs.Write(data, 0, data.Length);
        cs.FlushFinalBlock();
        byte[] encdata = ms.ToArray();
        cs.Close();
        rd.Clear();
        string erg = Convert.ToBase64String(encdata);
        return erg;
    }

    public byte[] DeCrypt(byte[] encdata, string pw)
    {
        //Zeichenkette mit eindeutigem Kennwort entschlüsseln
        RijndaelManaged rd = new RijndaelManaged();
        int rijndaelIvLength = 16;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        byte[] key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pw));
        md5.Clear();
        MemoryStream ms = new MemoryStream(encdata);
        byte[] iv = new byte[16];
        ms.Read(iv, 0, rijndaelIvLength);
        rd.IV = iv;
        rd.Key = key;
        CryptoStream cs = new CryptoStream(ms, rd.CreateDecryptor(), CryptoStreamMode.Read);
        byte[] data = new byte[ms.Length - rijndaelIvLength + 1];
        int i = cs.Read(data, 0, data.Length);
        cs.Close();
        rd.Clear();
        return data;
    }

    public string DeCrypt(string str, string pw)
    {
        //Zeichenkette mit eindeutigem Kennwort entschlüsseln
        RijndaelManaged rd = new RijndaelManaged();
        int rijndaelIvLength = 16;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        byte[] key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pw));
        md5.Clear();
        byte[] encdata = Convert.FromBase64String(str);
        MemoryStream ms = new MemoryStream(encdata);
        byte[] iv = new byte[16];
        ms.Read(iv, 0, rijndaelIvLength);
        rd.IV = iv;
        rd.Key = key;
        CryptoStream cs = new CryptoStream(ms, rd.CreateDecryptor(), CryptoStreamMode.Read);
        byte[] data = new byte[ms.Length - rijndaelIvLength + 1];
        int i = cs.Read(data, 0, data.Length);
        cs.Close();
        rd.Clear();
        string erg = System.Text.Encoding.UTF8.GetString(data, 0, i);
        return erg;
    }

    public string FileCrypt(string file, string pw)
    {
        //Datei verschlüsseln
        if (File.Exists(file))
        {
            //Archiv wird im Originalverzeichnis mit neuem Suffix gesichert 
            string NewName = file + CryptSuffix;
            if (File.Exists(NewName))
            {
                //kein Komprimieren, wenn Archiv bereits existiert 
                return "Die verschlüsselte Datei '" + NewName + "' zur Quelldatei '" + file +
                  "' existiert bereits. Eine Verschlüsselung wurde nicht durchgeführt!";
            }
            else
            {
                //gesamte Datei ohne Abspaltung eines Headers lesen
                byte[] oData = GetFile(file, false, ref pw);
                //Ausgangsdaten verschlüsseln und verschlüsselt in Byte-Array zurückschreiben
                if (pw.Length > 25)
                {
                    //ungültiges Kennwort, Abbruch mit Fehlermeldung!
                    return "Die maximale Kennwortlänge beträgt 25 Zeichen. Bitte geben Sie den Befehl mit einem anderen Kennwort erneut ein!";
                }
                if (pw == "")
                {
                    //ggfs. Standardkennwort verwenden
                    pw = ZipPw;
                }
                //Daten verschlüsseln
                oData = Crypt(oData, pw);
                //Header unverschlüsselt (!) in Datei schreiben 
                FileStream cFile = new FileStream(NewName, FileMode.Create);
                string header = ZipHeader;
                byte[] byteheader = Str2ByteArray(header);
                cFile.Write(byteheader, 0, byteheader.Length);
                //Kennwort als Hashwert speichern (sofern nicht angegeben vordefiniertes Kennwort ablegen),
                //wobei die Datenlänge auf 30 Zeichen ausgelegt wird
                string fpw = "";
                if (pw == "")
                {
                    fpw = String2Hash(ZipPw);
                }
                else
                {
                    fpw = String2Hash(pw);
                }
                if (fpw.Length < 30)
                {
                    //linksbündig auffüllen (dann keine Änderung des Zeichenkettenendes erforderlich)
                    string fillstr = "";
                    fillstr = fillstr.PadLeft(30 - fpw.Length, ' ');
                    //Füllstring/Kennwort verketten, um einheitliche Länge im Header zu garantieren
                    fpw = fillstr + fpw;
                }
                //Kennwort mit 30 Byte Länge schreiben
                byte[] pwh = Str2ByteArray(fpw);
                cFile.Write(pwh, 0, pwh.Length);
                //verschlüsselte Daten schreiben
                cFile.Write(oData, 0, oData.Length);
                cFile.Close();
                return "Die Datei '" + file + "' wurde in die Datei '" +
                        NewName + "' verschlüsselt.";
            }
        }
        else
        {
            //Quelldatei nicht vorhanden!
            return "Die Datei '" + file + "' existiert nicht. Eine Verschlüsselung ist nicht möglich!";
        }
    }

    public string FileDecrypt(string file, string pw)
    {
        //wurde das Kennwort nicht angegeben, wird auf das interne Standardkennwort zurückgegriffen
        if (pw == "")
        {
            //ggfs. Standardkennwort verwenden
            pw = ZipPw;
        }
        string rValue = "Die angegebene zu entschlüsselnde Datei '" + file +
            "' existiert nicht. Eine Entschlüsselung ist nicht möglich!";
        //Datei dekomprimieren (ggfs. nur bei korrekter Angabe des Sicherungskennwortes möglich)
        if (System.IO.File.Exists(file))
        {
            if (IsCompressedOrCrypted(file))
            {
                //Dateiname wird falls nötig automatisiert mit Index hochgezählt 
                string uncFile = file.Replace(CryptSuffix, "");
                if (File.Exists(uncFile))
                {
                    //dekomprimierte Datei mit Index versehen 
                    uncFile = FileWithIndex(uncFile);
                }
                //komprimierte Daten lesen, dabei Header überlesen 
                string pwh = "";
                byte[] cData = GetFile(file, true, ref pwh);
                //wurde ggfs. das erforderliche Kennwort angegeben
                if (IsCorrectPassword(pw, ref pwh) == false)
                {
                    //das erforderliche Kennwort wurde nicht angegeben
                    return "Das für die Entschlüsselung übergebene Kennwort '" + pw +
                        "' ist nicht für die Entschlüsselung geeignet!";
                }
                //mit geprüften Kennwort entschlüsseln
                cData = DeCrypt(cData, pw);
                //Ergebnis auslesen und in Zieldatei ablegen 
                SaveFile(uncFile, cData);
                rValue = "Die verschlüsselte Datei  '" + file + "' wurde in die Datei '" +
                  uncFile + "' entschlüsselt! ";
            }
        }
        return rValue;
    }

    public bool IsCompressedOrCrypted(string file) 
    {
        //Prüfung, ob eine angegebene Datei verschlüsselt/koprimiert wurde
        bool checkvalue = false;
        string Header = "";
        //Datei vorhanden? 
        if (File.Exists(file))
        {
            //erforderliche Mindestlänge (=Header+1) vorhanden?
            if (FileLen(file) > ZipHeader.Length)
            {
                //erforderlicher Header vorhanden?
                //Header im binären Modus lesen 
                FileStream fs = new FileStream(file, FileMode.Open, FileAccess.ReadWrite);
                // Stream öffnen
                BinaryReader r = new BinaryReader(fs);
                // Zeiger auf den Anfang
                r.BaseStream.Seek(0, SeekOrigin.Begin);
                //Zipheaderlänge auslesen und mit erforderlichem Header im Zeichenkettenformat vergleichen
                Header = ByteArray2Str(r.ReadBytes(ZipHeader.Length));
                r.Close();
                //Header gibt Aufschluss über Komprimierung 
                if (Header == ZipHeader)
                {
                    checkvalue = true;
                }
            }
        }
        return checkvalue;
    }

    public bool IsCorrectPassword(string pw, ref string pwh)
    {
        // Kennwort kann zum Zugriffsschutz auf komprimierte und verschlüsselte Dateien zugewiesen werden!
        bool rValue = false;
        // wurde kein Kennwort übergeben, muss in der bearbeiteten Datei das Standardkennwort gesetzt sein!
        if (pw == "" && String2Hash(ZipPw) == pwh)
        {
            rValue = true;
        }
        if (pw != "" && String2Hash(pw) != String2Hash(ZipPw) && (String2Hash(pw) == pwh))
        {
            rValue = true;
        }
        return rValue;
    }
          
   }
}




